﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace magic8ballUtility
{
    /// <summary>
    /// Klasa koja služi za prebacivanje regularnih izraza
    /// u eNKA automate koji ih prihvaćaju
    /// </summary> 
    public class RegexToNFA
    {
        List<Stanje> listaStanja = new List<Stanje>();
        Stanje trenutnoStanje = null;
        Stanje zavrsnoStanje = null;
        
        int indeksPocetnogStanja;

        /// <summary>
        /// Javni konstruktor razreda koji prevodi predani regex u NFA
        /// </summary>
        /// <param name="regex">regularni izraz koji se prevodi</param>
        public RegexToNFA(string regex)
        {
            Prevedi(regex);
        }
        
        /// <summary>
        /// Metoda koja prevodi regex u NFA
        /// </summary>
        /// <param name="regex">regularni izraz koji se prevodi</param>
        private void Prevedi(string inputRegex)
        {
            char[] regex = inputRegex.ToCharArray();

            var pocetnoStanje = DodajStanje();
            indeksPocetnogStanja = pocetnoStanje.Id;
            trenutnoStanje = pocetnoStanje;
                        
            PrevediRekurzija(regex, 0);
        }

        /// <summary>
        /// Rekurzivno prevodi regularni izraz. Poziva sama sebe za podizraze u zagradama
        /// </summary>
        /// <param name="regex">regularni izraz koji se prevodi</param>
        /// <param name="indeksPozicije">indeks pozicije u regularnom izrazu</param>
        /// <returns>indeks pozicije na kojoj je stala obrada</returns>
        private int PrevediRekurzija(char[] regex, int indeksPozicije)
        {
            Stanje cvorUnija = trenutnoStanje;
            Stanje cvorNadovezivanje = trenutnoStanje;
            Stanje zavrsnoStanje = DodajStanje();
            zavrsnoStanje.Prihvatljivo = true;

            Stanje novoStanje = DodajStanje();
            novoStanje.DodajPrijelaz('€', zavrsnoStanje.Id);
            trenutnoStanje.DodajPrijelaz('€', novoStanje.Id);
            trenutnoStanje = novoStanje;
      
            while (true)
            {
                if (indeksPozicije >= regex.Length)
                    break;

                switch (regex[indeksPozicije])
                {
                    case '\\':
                        indeksPozicije++;
                        goto default;
                    case '*':
                        novoStanje = DodajStanje();
                        trenutnoStanje.DodajPrijelaz('€', cvorNadovezivanje.Id);
                        trenutnoStanje.DodajPrijelaz('€', novoStanje.Id);
                        trenutnoStanje.UkloniPrijelaz('€', zavrsnoStanje.Id);
                        cvorNadovezivanje.DodajPrijelaz('€', novoStanje.Id);
                        novoStanje.DodajPrijelaz('€', zavrsnoStanje.Id);
                        trenutnoStanje = novoStanje;
                        break;
                    case '|':
                        novoStanje = DodajStanje();
                        trenutnoStanje = novoStanje;
                        cvorUnija.DodajPrijelaz('€', novoStanje.Id);
                        novoStanje.DodajPrijelaz('€', zavrsnoStanje.Id);
                        cvorNadovezivanje = trenutnoStanje;
                        break;
                    case '(':
                        cvorNadovezivanje = trenutnoStanje;
                        trenutnoStanje.UkloniPrijelaz('€', zavrsnoStanje.Id);
                        indeksPozicije = PrevediRekurzija(regex, ++indeksPozicije);
                        trenutnoStanje.DodajPrijelaz('€', zavrsnoStanje.Id);
                        trenutnoStanje.Prihvatljivo = false;
                        break;
                    case ')':
                        trenutnoStanje = zavrsnoStanje;
                        return indeksPozicije;
                    case '$':
                        //ignoriramo - označava prazan niz
                        break;
                    default:
                        novoStanje = DodajStanje();
                        char c = (regex[indeksPozicije] == '_' && indeksPozicije>0 && regex[indeksPozicije-1]=='\\') ? ' ' : regex[indeksPozicije];
                        trenutnoStanje.DodajPrijelaz(c, novoStanje.Id);
                        trenutnoStanje.UkloniPrijelaz('€', zavrsnoStanje.Id);
                        novoStanje.DodajPrijelaz('€', zavrsnoStanje.Id);
                        cvorNadovezivanje = trenutnoStanje;
                        trenutnoStanje = novoStanje;
                        break;
                }
                indeksPozicije++;
            }
            return indeksPozicije;
        }

        /// <summary>
        /// Dodaje novo stanje na kraj liste stanja
        /// </summary>
        /// <returns>novo stanje</returns>
        private Stanje DodajStanje()
        {
            Stanje novoStanje = new Stanje()
            {
                Id=listaStanja.Count
            };
            listaStanja.Add(novoStanje);
            return novoStanje;
        }

        /// <summary>
        /// Dohvaća stvoreni automat
        /// </summary>
        /// <returns>lista stanja novog automata</returns>
        public List<Stanje> DohvatiAutomat()
        {
            return listaStanja;
        }

        /// <summary>
        /// Predstavlja jedno stanje NKA
        /// </summary>
        public class Stanje
        {
            private IDictionary<char,ISet<int>> prijelazi = new Dictionary<char,ISet<int>>();
            
            /// <summary>
            /// Jedinstveni identifikator stanja
            /// </summary>
            public int Id { get; set; }

            /// <summary>
            /// Označava je li stanje prihvatljivo
            /// </summary>
            public bool Prihvatljivo { get; set; }

            /// <summary>
            /// Dodaje novi prijelaz. Ne moramo brinuti oko višestrukog 
            /// dodavanja, u pozadini je set kolekcija koja se pobrine 
            /// da svaki element spremi bar jednom
            /// </summary>
            /// <param name="c">ulazni znak za prijelaz</param>
            /// <param name="sljedeceStanje">indeks sljedećeg stanja</param>
            public void DodajPrijelaz(char c, int sljedeceStanje)
            {
                if (!prijelazi.ContainsKey(c))
                    prijelazi[c] = new HashSet<int>();
                prijelazi[c].Add(sljedeceStanje);
            }

            /// <summary>
            /// Uklanja prijelaz
            /// </summary>
            /// <param name="c">znak prijelaza</param>
            /// <param name="sljedeceStanje">sljedece stanje prijelaza</param>
            public void UkloniPrijelaz(char c, int sljedeceStanje)
            {
                if (prijelazi.ContainsKey(c))
                    prijelazi[c].Remove(sljedeceStanje);
            }

            /// <summary>
            /// Dohvaća sve prijelaze za dani znak
            /// </summary>
            /// <param name="c">znak</param>
            /// <returns>svi prijelazi za dani znak</returns>
            public ISet<int> DohvatiPrijelaze(char c)
            {
                if (prijelazi.ContainsKey(c))
                    return prijelazi[c];
                else return new HashSet<int>();
            }

            /// <summary>
            /// Dohvaća parove znakova i skupova indeksa sljedećih stanja
            /// </summary>
            /// <returns></returns>
            public IEnumerable<KeyValuePair<char, ISet<int>>> DohvatiPrijelaze()
            {
                foreach (var par in prijelazi)
                    yield return par;
            }
        }
    }
}
